// rgb-thing - a driver for controlled RGB lighting
// by Joe Hohertz  - started 20 January 2010
// see: http://rgb-thing.googlecode.com
//
// portions from Lux Spectralis written by Dale Wheat are used in this code
// see: http://dalewheat.com/luxspectralis/

// device = ATtiny2313
// clock = internal 8 MHz oscillator, longest startup time
// XXX: soon to be 16, just prototyping on a 10Mhz part...
//
// See LICENSE.TXT for terms of use, and copyright information


#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/pgmspace.h>
#include "common.h"
#include "vm.h"
//#include "delay.h"
#include "prand.h"
#include "sleep.h"

typedef enum {
	MODE_POWER_ON = 0, // power on behavior
	MODE_WHITE_5MIN = 0,
	MODE_WHITE_30MIN,
	MODE_WHITE_25,
	MODE_WHITE_50,
	MODE_WHITE_100,
	MODE_RED_25,
	MODE_RED_100,
	MODE_GREEN_25,
	MODE_GREEN_100,
	MODE_BLUE_25,
	MODE_BLUE_100,
	MODE_YELLOW_25,
	MODE_YELLOW_100,
	MODE_AQUA_25,
	MODE_AQUA_100,
	MODE_VIOLET_25,
	MODE_VIOLET_100,
	MODE_WASH1_25,
	MODE_WASH1_100,
	MODE_WASH2_25,
	MODE_WASH2_100,
	MODE_WASH3_25,
	MODE_WASH3_100,
	MODE_WASH4_25,
	MODE_WASH4_100,
	MODE_FLASH_WHITE,
	MODE_FLASH_RED,
	MODE_FLASH_GREEN,
	MODE_FLASH_BLUE,
	MODE_FLASH_RED_GREEN,
	MODE_FLASH_RED_BLUE,
	MODE_FLASH_GREEN_BLUE,
	MODE_BLINK_WHITE,
	MODE_BLINK_RED,
	MODE_BLINK_GREEN,
	MODE_BLINK_BLUE,
	MODE_MAX // off
} MODE;

const uint8_t staticRgbTab[15] PROGMEM = { 
  0xFD,			// White @ 25%
  0xFE,			// White @ 50%
  0xFF,			// White @ 100%
  0xC1,			// Red @ 25%
  0xC3,			// Red @ 100%
  0x31,			// Green @ 25%
  0x33,			// Green @ 100%
  0x0D,			// Blue @ 25%
  0x0F,			// Blue @ 100%
  0xF1,			// Yellow @ 25%
  0xF3,			// Yellow @ 100%
  0x3D,			// Aqua @ 25%
  0x3F,			// Aqua @ 100%
  0xCD,			// Violet @ 25%
  0xCF			// Violet @ 100%
};

struct colorWashRgbElement {
	uint8_t delay;       // delay value
	uint8_t bright;    // brightness value for ALL (0 to 255)
} const colorWashRgbTab[] PROGMEM = {
  { 1, 0x40 },		// fast, dim
  { 1, 0xFF },		// fast, bright
  { 3, 0x40 },		// moderate, dim
  { 3, 0xFF },		// moderate, bright
  { 10, 0x40 },		// slow, dim
  { 10, 0xFF },		// slow, bright
  { 100, 0x40 },	// really slow, dim
  { 100, 0xFF }		// really slow, bright
};

struct flashRgbElement {
	uint8_t crgbi1;     // first crgbi value
	uint8_t time1;      // first delay
	uint8_t crgbi2;     // second crgbi value
	uint8_t time2;      // second delay
} const flashRgbTab[11] PROGMEM = {
  { 0xFF, 73, 0x00, 73 }, // flash white
  { 0xC3, 73, 0x00, 73 }, // flash red
  { 0x33, 73, 0x00, 73 }, // flash green
  { 0x0F, 73, 0x00, 73 }, // flash blue
  { 0xC3, 73, 0x33, 73 }, // flash red & green
  { 0xC3, 73, 0x0F, 73 }, // flash red & blue
  { 0x33, 73, 0x0F, 73 }, // flash green & blue
  { 0xFF, 2, 0x00, 144 }, // blink white
  { 0xC3, 5, 0x00, 141 }, // blink red
  { 0x33, 5, 0x00, 141 }, // blink green
  { 0x0F, 5, 0x00, 141 }  // blink blue
};

// this is used to preserve the value across reset
volatile MODE mode __attribute__ ((section (".noinit")));

///////////////////////////////////////////////////////////////////////////////
// init() - initialize everything
// note:  this "function" is in section .init3 so it is executed before main()
///////////////////////////////////////////////////////////////////////////////

void init(void) __attribute__ ((naked, section(".init3")));
void init(void) {
	// determine cause of device reset;  act accordingly
	if(bit_is_set(MCUSR, PORF)) {
		// fresh power on
		mode = MODE_POWER_ON; // power on!
	} else if(bit_is_set(MCUSR, EXTRF)) {
		// user triggered the reset button, used here to advance modes
		mode++; // advance mode
		if(mode > MODE_MAX) {
			mode = MODE_POWER_ON; // reset mode
		}
	}

	MCUSR = 0; // reset bits so the result on next reset is reliable

	// initialize ATtiny2313 input & output ports
	PORTA = 1<<PORTA2;		// set reset high? XXX why should we need this? ahh pull ups
	//PORTB = 0x00;
	//PORTD = 0x00;

	// setup Timer/Counter0 Control Registers
	TCCR0B = 0<<FOC0A | 0<<FOC0B | 0<<WGM02 | 0<<CS02 | 0<<CS01 | 1<<CS00;
	TCCR0A = 1<<COM0A1 | 0<<COM0A0 | 1<<COM0B1 | 0<<COM0B0 | 1<<WGM01 | 1<<WGM00;
	// setup Timer/Counter1 Control Registers
	TCCR1B = 0<<ICNC1 | 0<<ICES1 | 0 << WGM13 | 1<<WGM12 | 0<<CS12 | 0<<CS11 | 1<<CS10;
	TCCR1A = 1<<COM0A1 | 0<<COM0A0 | 1<<COM0B1 | 0<<COM0B0 | 0<<WGM01 | 1<<WGM00;
	TCCR1C = 0x00;		// always should do after setting TCCR1A
	GTCCR |= _BV(PSR10);	// reset/sync the timer prescaler now we're all setup to run, before we start using it
	// set our data directions
	DDRB = 1<<DDRLEDR | 1<<DDRLEDG | 1<<DDRLEDB;
	DDRD = 1<<DDRLEDI;
	// initialize interrupts.
	TIMSK = 0<<OCIE0B | 0<<OCIE0A | 1<<TOIE0; // interrupts
	sei(); // enable global interrupts
}

///////////////////////////////////////////////////////////////////////////////
// main() - main program function
///////////////////////////////////////////////////////////////////////////////

void main(void) {
	// still being lux-like, so reset is how user changes happen, and we initialize last mode from current before looping
	MODE lastmode;
	lastmode = mode+1;

	// main gets to be an infinite loop...
	// loops, continuously stepping the program, watching for system exit events, and handing exterior controls when they come
	while(1) {
		// first we check if the mode changed:
		if(mode != lastmode) {
			// store new lastmode, we done need to know where we were anymore
			lastmode = mode;
			if (mode >= MODE_WHITE_5MIN && mode <= MODE_WHITE_30MIN) {
				// timed_light type mode, this trick works for our two modes for now:
				load_program(0);
				reg[15] = (mode == MODE_WHITE_5MIN) ? 42 : 255;
			} else if (mode >= MODE_WHITE_25 && mode <= MODE_VIOLET_100) {
				// lookup values from table, set registers, load 'static' program in
				load_program(1);
				int tabidx = mode - MODE_WHITE_25;
				reg[15] = pgm_read_byte(&staticRgbTab[tabidx]);
			} else if (mode >= MODE_WASH1_25 && mode <= MODE_WASH4_100) {
				// lookup values from table, set registers, load 'wash' program in
				load_program(2);
				int tabidx = mode - MODE_WASH1_25;
				reg[9] = pgm_read_byte(&colorWashRgbTab[tabidx].delay);
				reg[8] = pgm_read_byte(&colorWashRgbTab[tabidx].bright);
			} else if (mode >= MODE_FLASH_WHITE && mode <= MODE_BLINK_BLUE) {
				// lookup values from table, set registers, load 'wash' program in
				load_program(3);
				int tabidx = mode - MODE_FLASH_WHITE;
				reg[15] = pgm_read_byte(&flashRgbTab[tabidx].crgbi1);
				reg[14] = pgm_read_byte(&flashRgbTab[tabidx].time1);
				reg[13] = pgm_read_byte(&flashRgbTab[tabidx].crgbi2);
				reg[12] = pgm_read_byte(&flashRgbTab[tabidx].time2);
			} else if (mode == MODE_MAX) {			// deep sleep mode
				deepsleep();
			//} else {
			}
		}

		// step the program:
		step_program();

	} // end of loop, go for another round...;
}

///////////////////////////////////////////////////////////////////////////////
// timer/counter0 overflow interrupt handler
///////////////////////////////////////////////////////////////////////////////

ISR(TIMER0_OVF_vect) __attribute__ ((naked));
ISR(TIMER0_OVF_vect) {
	asm("reti"); // return from interrupt
}

///////////////////////////////////////////////////////////////////////////////

// [end-of-file]

